/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */
#pragma once

#include <lib3d/hard/Hardware.h>
#include "Dependent.h"
#include "Texture.h"
#include "VertexBuffer.h"
#include "IndexBuffer.h"
#include "PointBuffer.h"
#include "RenderTarget.h"
#include "AdapterInfo.h"
#include <d3d9.h>
#include <NoCopy.h>
#include <lib3d/hard/BlendMode.h>

#include <boost/scoped_ptr.hpp>

namespace liba
{
namespace lib3d
{
namespace hard
{
namespace directx8
{
///class Texture;
///class IndexBuffer;
///class VertexBuffer;

class Hardware : public hard::Hardware, private Nocopy
{
	Matrix curr_mat[1024];
	int curr_pos;
	int old_pos;

	IDirect3D9 * direct3d;
	std::string device_name;
	std::string hardware_name;
	AdapterInfo adapterInfo;

	bool can_render_sprites;
	int max_vertex_index;
	bool can_render_mipmaps;
	bool can_render_shadows;
	DWORD stencil_incop;
	DWORD stencil_decop;
	bool can_render_alpha;
	bool try_hardware_vp;
	bool use_hardware_vshaders;
	enum style_enum { SLOW, PS00, PS13, PS14 }; // Type of the hardware to use
	style_enum style;
	bool prefer_16bit_textures;
	int max_anisotropy;
	int max_texture_size;

	struct Statistics
	{
		int triangles;
		int vertices;
		int shadow_triangles;
		int shadow_vertices;
		int sprites;
		bool is_shadow;

		Statistics()
		:	is_shadow( false ),
			triangles( 0 ),
			shadow_triangles( 0 ),
			vertices( 0 ),
			shadow_vertices( 0 ),
			sprites( 0 )
		{}
	};
	Statistics stats;

	// Parameters
	struct Parameters
	{
		bool need_cubic_rendertarget;
		bool need_8_rt_color_channel_bits; // for motion blur effects, other accumulations
		int shadow_passes;     // 0..inf, if > 0 require 8bit stencil
		// Selections
		bool is_fullscreen;    // true/false
		int resolution_x;      // 320/512/...
		int resolution_y;      // 240/384/...
		int lighting_passes;   // 0..inf
		// Dependent parameters
		D3DFORMAT color_format;
		D3DFORMAT display_format;
		D3DFORMAT render_target_format;
		D3DFORMAT z_buffer_format;

		D3DFORMAT format_for_texture[pictureformat::FORMAT_SIZE];
		D3DFORMAT format_for_cubic_texture[pictureformat::FORMAT_SIZE];
		D3DMULTISAMPLE_TYPE multisample_type;
	};
	Parameters parameters;

	struct Tables
	{
		IDirect3DTexture9 * dummy_0_0_0_0;
		IDirect3DTexture9 * dummy_05_05_1_1;
		IDirect3DTexture9 * dummy_1_1_1_1;

		Tables(IDirect3DDevice9 * device, style_enum style, D3DFORMAT format_for_texture);
		~Tables();
	};
	boost::scoped_ptr<Tables> tables;

	void do_need_cubic_rendertarget(Parameters * parameters);
	void do_need_8_rt_color_channel_bits(Parameters * parameters);
	void do_shadow_passes(Parameters * parameters);
	void do_is_fullscreen(Parameters * parameters);
	bool fill_dependent_parameters(Parameters * parameters);

	D3DVIEWPORT9 viewport; // Set by set_params_for_start_scene()
	enum { MAX_TEXTURES = 8 };
	IDirect3DDevice9 * device;

	void set_lights( RenderList * render_list );

	struct DeviceObjects
	{
		IDirect3DSurface9 * render_target_default;
		IDirect3DSurface9 * z_buffer_surface;

		DWORD VSSprite;

		IDirect3DSurface9 * lock_surf1;
		IDirect3DSurface9 * lock_surf2;
		bool first_lock;

		DeviceObjects(IDirect3DDevice9 * device, style_enum style, bool use_hw_shaders, D3DFORMAT render_target_format);
		void recreate_z_buffer_surface(int wi, int he, D3DFORMAT z_buffer_format);
		void issue_frame_query();
		void wait_frame_query();
		~DeviceObjects();
	private:
		IDirect3DDevice9 * device;
		bool use_hw_shaders;
	};

	boost::scoped_ptr<DeviceObjects> device_objects;

	RECT current_rect;
	LONG window_styles;
	D3DPRESENT_PARAMETERS present_parameters; 

	Dependent::List dependent_list;
	typedef std::list<directx8::RenderTarget *> RenderTargets;
	RenderTargets render_targets;

	RenderList * current_render_list;
	BlendMode current_blend_mode;

	void lose_resources();
	void restore_resources();
protected:
	virtual bool set_mode_internal(WindowImpl * window, const Strings & attribute_values);
public:
	static converter::EnumPair<char,D3DFORMAT> d3dformat_names[];

	virtual void push_matrix( const Matrix & mat )
	{
		device->SetTransform( D3DTS_WORLD, (D3DMATRIX*) &mat );
		curr_mat[++curr_pos] = mat;
		old_pos = curr_pos;
	}
	virtual void push_mult_matrix( const Matrix & mat )
	{
		if (old_pos != curr_pos )
			device->SetTransform( D3DTS_WORLD, (D3DMATRIX *)&curr_mat[curr_pos] );
		device->MultiplyTransform( D3DTS_WORLD, (D3DMATRIX*) &mat );
		device->GetTransform( D3DTS_WORLD, (D3DMATRIX *)&curr_mat[++curr_pos] );
		old_pos = curr_pos;
	}
	virtual const Matrix & get_matrix()const
	{
		return curr_mat[curr_pos];
	}
	virtual void pop_matrix()
	{
		--curr_pos;
	}

	RenderList * get_current_render_list()
	{
		return current_render_list;
	}

	// non-const because we wish std::map<...>::operator[]
	Hardware( LPDIRECT3D9 direct3d, AdapterInfo adapterInfo, std::map<std::string,int> & caps_map );
	virtual ~Hardware();
	virtual std::string get_hardware_name();
	// for mode negotiation and setting
	virtual std::string get_name();
	virtual void stop_mode();

	virtual void render( RenderList * render_list, RenderTarget * render_target );

	virtual hard::Texture * create_texture(int mipmap_levels, const pictureformat::Format & desired_format, int width, int height);
	virtual hard::TextureCubic * create_texture_cubic(int mipmap_levels, const pictureformat::Format & desired_format, int width, int height);

	virtual hard::VertexBuffer * create_vertex_buffer( bool dynamic );
	virtual hard::IndexBuffer * create_index_buffer();
	virtual hard::PointBuffer * create_point_buffer();
	virtual hard::RenderTarget * create_render_target(const Atom & render_target_name, int width, int height);
	virtual hard::TextureBase * get_render_target(const Atom & render_target_name);

	virtual bool start_scene(double left, double right, double top, double bottom);
	virtual const int get_width()const;
	virtual const int get_height()const;
	virtual bool start_frame();
	virtual void flip();
	virtual void get_data();

	Dependent::List::iterator add_dependent(Dependent * dep)
	{
		return dependent_list.insert( dependent_list.end(), dep );
	}
	void remove_dependent( Dependent::List::iterator it )
	{
		dependent_list.erase( it );
	}
	virtual bool is_operational()const
	{
		return device_objects;
	}
	D3DFORMAT remap_pictureformat(const pictureformat::Format & desired_format);
	pictureformat::Format remap_pictureformat(D3DFORMAT desired_format);
	IDirect3DDevice9 * get_device()const
	{
		return device;
	}
	bool can_hw_vshaders()const
	{
		return use_hardware_vshaders;
	}
	const RECT & get_current_viewport_rect()const
	{
		return current_rect;
	}
	static DWORD get_dword_color(const Color & color, double alpha);
	static DWORD get_dword_color(double r, double g, double b, double alpha);
	void remove_render_target(directx8::RenderTarget * rt)
	{
		render_targets.remove( rt );
	}
	void set_params_for_start_scene(int left, int right, int top, int bottom);
	void recreate_z_buffer_surface();

private:
	int device_memory;
	void report_device_memory();
	void render_emissive( const RenderList & render_list, RenderTarget * render_target );

	void set_fog( const Color & color, bool enable );
	void set_texture_alt( int stage, TextureBase * tex, IDirect3DBaseTexture9 * alt_tex );
	void finish_scene();

	void set_culling(DWORD cull_order);
	void set_z_less_write();
	void set_z_less_nowrite();
	void set_z_lessequal_nowrite();
	void set_z_lessequal_write();
	void combiner_blend_for_sprite_pass();
	void set_alpha_test_discard_only_0();
	void set_alpha_test_keep_only_1();
	void set_alpha_test_off();
	void set_alpha_test_on();
	void combiner_blend_for_diffuse_reflecion_emissive_pass();
	void set_blend_mode(BlendMode bm);

	void tex_ren_sprite( const RenderList & render_list, const RenderSprite & render_sprite );

	void ren_model( const RenderList & render_list, const RenderModel & render_model );
	void ren_model( const RenderList & render_list, const RenderSprite & render_model );

	void con_tex_ren_model_diffuse_reflecion_emissive( const RenderList & render_list, const RenderModel & render_model );
	void con_tex_ren_model_diffuse_reflecion_emissive( const RenderList & render_list, const RenderSprite & render_model );

	bool start_render( const RenderList & render_list, RenderTarget * render_target );
};

} // namespace directx8
} // namespace hard
} // namespace lib3d

namespace converter
{
template<>
struct ConverterTraits<char,D3DFORMAT>
	:	public ConverterTraitsEnumBase<char, D3DFORMAT, lib3d::hard::directx8::Hardware::d3dformat_names>
{};
}

} // namespace liba
using namespace liba;

